using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Drawing.Imaging;

namespace ENGINE
{
    class BazisMatrix
    {
        public double[,] Matrix;
        public BazisMatrix(ref Buffers sender)
        {
            Matrix = new double[4, 3];
            for (byte i = 0; i != 4; i++)
                for (byte j = 0; j != 3; j++)
                    Matrix[i, j] = sender.Bazis[i, j];
        }
        public void SetToBuffer(ref Buffers source)
        {            
            for (byte i = 0; i != 4; i++)
                for (byte j = 0; j != 3; j++)
                    source.Bazis[i, j] = Matrix[i, j];
        }

    }
    
    class Buffers
    {
        public Bitmap bitmap;
        protected IntPtr PTR;
        private Rectangle rect;
        private bool bitmapExist=false;
        protected System.Drawing.Graphics DC;
        private Byte[] ColorBuf;
        private Byte[] ClearBuf;
        private BitmapData bitmapData;
        private double[] ZBuf;
        private double DepthZero = 1000;
        public Int16 Scale;
        public double[,] Bazis;
        private Int16 Top;
        private Int16 Bottom;
        private Int16 Left;
        private Int16 Right;
        private Point TopLeft;
        private double z0 = -5;
        private double zNear = 1004.5;
        private Int16 Width;
        private Int16 Height;
        private Byte TempRed;
        private Byte TempGreen;
        private Byte TempBlue;
        private Byte ClearRed=255;
        private Byte ClearGreen=255;
        private Byte ClearBlue=255;
        private double LightKoef;
        public double ShadowKoef = 0.5;
        private double[] LightPos ={ 0, 0, 1};
        public double LightMin = 0.3;
        public int TrianglesCount;

        public bool EnableLightPos =true;
        public bool EnableLighting=true;
        public bool EnableTexture =true;
        public bool EnableDoubleNormals = false;
        public bool EnableBackFace = false;
        public bool EnableCullFace = true;
        
        public void SetLightPos(double x,double y,double z)
        {
            double tmp = Math.Sqrt(x * x + y * y + z * z);
            LightPos[0] = x/tmp;
            LightPos[1] = y/tmp;
            LightPos[2] = z/tmp;
        }

        public Buffers()
        {
            Bazis = new double[4, 3];
            ZeroBazis();            
        }
        public void ZeroBazis()
        {
            for (byte i = 0; i != 4; i++)
                for (byte j = 0; j != 3; j++)
                    Bazis[i, j] = (i == j) ? 1 : 0;
        }

        [DllImport("kernel32.dll")]public static extern  Int32 GetTickCount();

        public void SetZ0(double z0, double ZNear)
        {
            z0 = Math.Abs(z0);
            ZNear = Math.Abs(ZNear);
            if ((z0 >1) && (ZNear < z0))
            {
                this.z0 = -z0;
                this.zNear = ZNear+DepthZero;
            }
        }
        public void ClearColor(byte Red, Byte Green, Byte Blue)
        {
            this.ClearRed = Red;
            this.ClearGreen = Green;
            this.ClearBlue = Blue;
        }
        public void ClearColor(Color color)
        {
            this.ClearRed = color.B;
            this.ClearGreen = color.G;
            this.ClearBlue = color.R;
        }
        
        private void Rotate(Int16 Index1, Int16 Index2,double Angle)
        {
           double[] Temp=new double[3];
           double ccos=Math.Cos(Angle);
           double ssin=Math.Sin(Angle);
           for (Byte i=0;i<=2;i++)
               {
               Temp[i]=-Bazis[Index1,i];
               Bazis[Index1,i]=Bazis[Index1,i]*ccos+Bazis[Index2,i]*ssin;
               }
            for (Byte i=0;i<=2;i++)
               Bazis[Index2,i]=Temp[i]*ssin+Bazis[Index2,i]*ccos;
        }
        public void RotateX(double Angle){Rotate(1,2,Angle);}
        public void RotateY(double Angle){Rotate(0,2,Angle);}
        public void RotateZ(double Angle){Rotate(0,1,Angle);}
        public void Translatef(double x,double y,double z)
            {for (byte i=0;i<=2;i++)
                Bazis[3,i]+=x*Bazis[0,i]-y*Bazis[1,i]+z*Bazis[2,i];
            }
        public void Scalef(double x,double y,double z)
        {
        for (byte i=0;i!=3;i++)
            {
            Bazis[0,i]*=x;
            Bazis[1,i]*=y;
            Bazis[2,i]*=z;
            }
        }
        public void Scalef(double Scale)
        {
            for (byte i = 0; i != 3; i++)
            {
                Bazis[0, i] *= Scale;
                Bazis[1, i] *= Scale;
                Bazis[2, i] *= Scale;
            }
        }
        public void Viewport(IntPtr Handle,int Left,int Top, int Right, int Bottom)
        {
            if ((Bottom > Top) && (Right > Left))
                {
                this.DC = System.Drawing.Graphics.FromHwnd(Handle);
                this.Width = (Int16)(Right - Left);
                this.Height = (Int16)(Bottom - Top);
                this.Top = (Int16)(Top);
                this.Bottom = (Int16)(Bottom);
                this.Left = (Int16)(Left);
                this.Right = (Int16)(Right);
                this.Scale = (Int16)(Width / 5.2);
                this.TopLeft = new Point(Left, Top);
                
                if (bitmapExist)
                    {
                     bitmap.Dispose();
                    }
                bitmapExist = true;
                ColorBuf = new Byte[this.Width * this.Height * 4];
                ClearBuf = new Byte[this.Width * this.Height * 4];
                bitmap  = new Bitmap(Width, Height, PixelFormat.Format32bppArgb);                                                  
                rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);                                       
                ZBuf = new double[(Right - Left + 1) * (Bottom - Top + 1)];                                
            }
        }
        public void FillClearBuf()
        {
            for (Int32 i = 0; i < ColorBuf.Length; i += 4)
            {
                ClearBuf[i] = ClearBlue; ClearBuf[i + 1] = ClearGreen;
                ClearBuf[i + 2] = ClearRed; ClearBuf[i + 3] = 255;
            }            
        }
        public void FillClearBuf(ref Texture BaseImage)
        {
            for (Int16 i = 0; i < this.Width; i++)
            {
                Int16 imageX = (Int16)(BaseImage.Width-1- i * (BaseImage.Width-1) / (this.Width-1));
                for (Int16 j = 0; j < this.Height; j++)
                {
                    Int16 imageY = (Int16)(BaseImage.Height - 1 - j * (BaseImage.Height - 1) / (this.Height - 1));
                    Int32 Num=(j * this.Width + i) * 4;
                    Int32 imageNum=(imageY*BaseImage.Width+imageX)*3;
                    ClearBuf[Num + 2] = (byte)(ClearBlue * BaseImage.ColorBuf[imageNum] / 255);
                    ClearBuf[Num + 1] = (byte)(ClearGreen*BaseImage.ColorBuf[imageNum+1] / 255);
                    ClearBuf[Num] = (byte)(ClearRed*BaseImage.ColorBuf[imageNum+2] / 255);
                    ClearBuf[Num + 3] = 255;
                }
            }
        }
        public void Clear()
        {
            System.Array.Copy(ClearBuf, 0, ColorBuf, 0, ColorBuf.Length);
            Array.Clear(ZBuf,0, ZBuf.Length);
            TrianglesCount = 0;
        }
        private class TPoint3D
        {
            public Int16 x;
            public Int16 y;
            public double z;
            public double TexX;
            public double TexY;
            public TPoint3D(Int32 x, Int32 y, double z)
            { this.x = (Int16)x; this.y = (Int16)y; this.z = z; }
            };
        private TPoint3D ProectToScreen(int NumVertex, ref double[] Points, 
            ref double[] TexCoords, ref double P1, ref double P2, ref double P3)
            {
            double[] FirstPoint = new double[3];
            for (byte i=0;i<3;i++)
                FirstPoint[i]=Bazis[3,i]+(Points[NumVertex*3]*Bazis[0,i]+
                                        Points[NumVertex*3+1]*Bazis[1,i]+
                                        Points[NumVertex*3+2]*Bazis[2,i]);
            if (Math.Abs(FirstPoint[2]+z0)<0.01){FirstPoint[2]+=0.01;}
            Int32 x = (Int32)(Width/2+Scale * FirstPoint[0] / (1 + FirstPoint[2]/z0));
            Int32 y = (Int32)(Height/2-Scale * FirstPoint[1] / (1 + FirstPoint[2]/z0));
            P1 = FirstPoint[0]; P2 = FirstPoint[1]; P3 = FirstPoint[2];
            return new TPoint3D(x, y, FirstPoint[2]);
            }
        private double[] GetNormalEq(ref double[] Pts)
            {
               double Temp=(Pts[4]-Pts[1])*(Pts[8]-Pts[2])-(Pts[7]-Pts[1])*(Pts[5]-Pts[2]);
               double X=Temp;
               double D=-Pts[0]*Temp;

               Temp=(Pts[3]-Pts[0])*(Pts[8]-Pts[2])-(Pts[6]-Pts[0])*(Pts[5]-Pts[2]);
               D+=Pts[1]*Temp;
               double Y=-Temp;

               Temp=(Pts[3]-Pts[0])*(Pts[7]-Pts[1])-(Pts[4]-Pts[1])*(Pts[6]-Pts[0]);
               double Z=Temp;
               D+=-Pts[2]*Temp;

               double[] Res ={ X, Y, Z, D };
               return Res; 
            }
        private double GetLighting(ref double[] Pts,out bool Visible)
        {
            double[] Eq = GetNormalEq(ref Pts);
            double Res = Math.Sqrt(Eq[0] * Eq[0] + Eq[1] * Eq[1] + Eq[2] * Eq[2]);
            double X = (Pts[0] + Pts[3] + Pts[6]) / 3;
            double Y = (Pts[1] + Pts[4] + Pts[7]) / 3;
            double Z = (Pts[2] + Pts[5] + Pts[8]) / 3;
            double Res2= Math.Sqrt(X * X + Y * Y + (Z - z0) * (Z - z0));
            X = (Eq[0] * X + Eq[1] * Y + Eq[2] * (Z - z0)) / Res2;
            Visible = (X > 0);
            if (Res == 0) { return 0; }
            else
                return (EnableLightPos) ? (Eq[0] * LightPos[0] + Eq[1] * LightPos[1] + Eq[2] * LightPos[2]) / Res : X;
        }
        public void PaintFaces(ref double[] Points,ref double[] TexCoords,ref Int32[] Faces,bool UnitedColor,
                                                     ref int[] color,ref Texture texture,ref int[] TexFaces)
        {
            TPoint3D[] Temp=new TPoint3D[3];
            double[] Point3Df = new double[9];
            for (int i = 0; i < Faces.Length / 3; i++)
            {
                TrianglesCount++;
                for (byte j = 0; j != 3; j++)
                {
                    int num = Faces[i * 3 + j];
                    Temp[j] = ProectToScreen(Faces[i * 3 + j], ref Points, ref TexCoords
                                 , ref Point3Df[j * 3], ref Point3Df[j * 3 + 1], ref Point3Df[j * 3 + 2]);
                    if (EnableTexture)
                    {
                        Temp[j].TexX = TexCoords[TexFaces[i * 3 + j] * 2];
                        Temp[j].TexY = TexCoords[TexFaces[i * 3 + j] * 2 + 1];
                    }
                    Temp[j].z += DepthZero;
                }
                if ((Temp[0].z < zNear) || (Temp[1].z < zNear) || (Temp[2].z < zNear))
                {
                    bool Visible;
                    if (EnableLighting)
                    {
                        LightKoef = GetLighting(ref Point3Df,out Visible);
                        if (!EnableDoubleNormals && (LightKoef < LightMin))
                        { LightKoef = LightMin; }
                        else
                            LightKoef = Math.Pow(Math.Abs(LightKoef), ShadowKoef);
                    }
                    else { LightKoef = 1; Visible = true; }
                    if (Visible&&!EnableBackFace||!Visible&&EnableBackFace||!EnableCullFace||!EnableLighting)
                    {
                        Int32 ColorNum = UnitedColor ? 0 : i * 3;
                        TempRed = (Byte)(color[ColorNum] * LightKoef);
                        TempGreen = (Byte)(color[ColorNum + 1] * LightKoef);
                        TempBlue = (Byte)(color[ColorNum + 2] * LightKoef);
                        if (EnableTexture)
                        { PaintTriangle(ref Temp, ref texture); }
                        else PaintTriangle(ref Temp);
                    }
                }
            }                
        }   
     
        private void PaintPoint(Int32 X, Int32 Y,double Z,double TexX,double TexY,ref Texture texture)
            {
                if ((X>=0)&&(Y>=0)&&(X<Width)&&(Y<Height)&&(Z<zNear))
                    if (Z > ZBuf[Y * Width + X])
                    {                               
                        int Index = ((Int32)(TexY * (texture.Height-1)) * texture.Width + (Int32)(TexX * (texture.Width-1)))*3 ;
                        ColorBuf[(Y * Width + X) * 4    ] = (Byte)(TempBlue*texture.ColorBuf[Index+ 2]/255);
                        ColorBuf[(Y * Width + X) * 4 + 1] = (Byte)(TempGreen * texture.ColorBuf[Index + 1] / 255);
                        ColorBuf[(Y * Width + X) * 4 + 2] = (Byte)(TempRed * texture.ColorBuf[Index] / 255);
                        ColorBuf[(Y * Width + X) * 4 + 3] = 255;                        
                        ZBuf[Y * Width + X] = Z;
                    }
            }
        private void PaintPoint(Int32 X, Int32 Y, double Z)
        {
            if ((X >= 0) && (Y >= 0) && (X < Width) && (Y < Height) && (Z < zNear))
                if (Z > ZBuf[Y * Width + X])
                {
                    ColorBuf[(Y * Width + X) * 4] = TempBlue;
                    ColorBuf[(Y * Width + X) * 4 + 1] = TempGreen;
                    ColorBuf[(Y * Width + X) * 4 + 2] = TempRed;
                    ColorBuf[(Y * Width + X) * 4 + 3] = 255;
                    ZBuf[Y * Width + X] = Z;
                }
        }
        private void Swap(ref TPoint3D P1, ref TPoint3D P2)
            { TPoint3D P3 = P1; P1 = P2; P2 = P3; }
        public void SwapBuffers()
            {
                bitmapData = bitmap.LockBits(rect, ImageLockMode.ReadWrite, bitmap.PixelFormat);
                PTR = bitmapData.Scan0;     
                Marshal.Copy(ColorBuf, 0, PTR, ColorBuf.Length);
                bitmap.UnlockBits(bitmapData);
                DC.DrawImage(this.bitmap, TopLeft);
            }
        private void PaintTriangle(ref TPoint3D[] Pts,ref Texture texture)
            {
                if (Pts[0].y < Pts[1].y) { Swap(ref Pts[0], ref Pts[1]); }
                if (Pts[1].y < Pts[2].y) { Swap(ref Pts[1], ref Pts[2]); }
                if (Pts[0].y < Pts[1].y) { Swap(ref Pts[0], ref Pts[1]); }

                if (Pts[0].y == Pts[1].y)
                    {
                        Int16 dif = (Int16)Math.Abs(Pts[0].x - Pts[1].x);
                        if (dif==0) {dif=1;}
                        for (Int16 i = Math.Min(Pts[0].x, Pts[1].x); i <= Math.Max(Pts[0].x, Pts[1].x); i++)
                        {
                            double Part = ((double)Math.Abs(i - Pts[0].x)) / dif;
                            PaintPoint(i, Pts[0].y, Part*Pts[1].z+(1-Part)*Pts[0].z,
                                Part*Pts[1].TexX+(1-Part)*Pts[0].TexX,Part*Pts[1].TexY+(1-Part)*Pts[0].TexY,ref texture);}
                    }
                    else
                    {
                        Int16 dy1 = (Int16)(Pts[0].y - Pts[2].y);
                        Int16 dy2 = (Int16)(Pts[0].y - Pts[1].y);
                        for (Int16 j = Pts[1].y; j <= Pts[0].y; j++)
                        {
                            double Part1=(double)(Pts[0].y-j)/dy1;
                            double Part2 = (double)(Pts[0].y - j) / dy2;
                            Int16 LeftX = (Int16)(Part1 * Pts[2].x + (1 - Part1) * Pts[0].x);
                            Int16 RightX = (Int16)(Part2 * Pts[1].x + (1 - Part2) * Pts[0].x);
                            double LeftZ= Part1*Pts[2].z+(1-Part1)*Pts[0].z;
                            double RightZ = Part2*Pts[1].z+(1-Part2)*Pts[0].z;
                            double LeftTexX= Part1*Pts[2].TexX+(1-Part1)*Pts[0].TexX;
                            double RightTexX = Part2*Pts[1].TexX+(1-Part2)*Pts[0].TexX;                            
                            double LeftTexY= Part1*Pts[2].TexY+(1-Part1)*Pts[0].TexY;
                            double RightTexY = Part2*Pts[1].TexY+(1-Part2)*Pts[0].TexY;
                            Int16 dif = (Int16)Math.Abs(LeftX - RightX); if (dif == 0) { dif = 1; }
                            for (Int16 i = Math.Min(LeftX, RightX); i <= Math.Max(LeftX, RightX); i++)
                            {
                                double Part = ((double)Math.Abs(i - LeftX)) / dif;        
                                PaintPoint(i, j, Part*RightZ+(1-Part)*LeftZ,
                                Part * RightTexX + (1 - Part) * LeftTexX, Part * RightTexY + (1 - Part) * LeftTexY, ref texture);
                            }
                        }
                    }

            ////////////\\\\\\\\\\\////////\\\\\\\\\\\\
                    if (Pts[1].y == Pts[2].y)
                    {
                        Int16 dif = (Int16)Math.Abs(Pts[1].x - Pts[2].x);
                        if (dif == 0) { dif = 1; }
                        for (Int16 i = Math.Min(Pts[2].x, Pts[1].x); i <= Math.Max(Pts[2].x, Pts[1].x); i++)
                        {
                            double Part = ((double)Math.Abs(i - Pts[2].x)) / dif;
                            PaintPoint(i, Pts[1].y, Part * Pts[1].z + (1 - Part) * Pts[2].z,
                                Part * Pts[1].TexX + (1 - Part) * Pts[2].TexX, Part * Pts[1].TexY + (1 - Part) * Pts[2].TexY, ref texture);
                        }
                    }
                    else
                    {
                        Int16 dy1 = (Int16)(Pts[0].y - Pts[2].y);
                        Int16 dy2 = (Int16)(Pts[1].y - Pts[2].y);
                        for (Int16 j = Pts[2].y; j <= Pts[1].y; j++)
                        {
                            double Part1 = (double)(j-Pts[2].y) / dy1;
                            double Part2 = (double)(j-Pts[2].y) / dy2;
                            Int16 LeftX = (Int16)(Part1 * Pts[0].x + (1 - Part1) * Pts[2].x);
                            Int16 RightX = (Int16)(Part2 * Pts[1].x + (1 - Part2) * Pts[2].x);
                            double LeftZ = Part1 * Pts[0].z + (1 - Part1) * Pts[2].z;
                            double RightZ = Part2 * Pts[1].z + (1 - Part2) * Pts[2].z;
                            double LeftTexX = Part1 * Pts[0].TexX + (1 - Part1) * Pts[2].TexX;
                            double RightTexX = Part2 * Pts[1].TexX + (1 - Part2) * Pts[2].TexX;
                            double LeftTexY = Part1 * Pts[0].TexY + (1 - Part1) * Pts[2].TexY;
                            double RightTexY = Part2 * Pts[1].TexY + (1 - Part2) * Pts[2].TexY;
                            Int16 dif = (Int16)Math.Abs(LeftX - RightX); if (dif == 0) { dif = 1; }
                            for (Int16 i = Math.Min(LeftX, RightX); i <= Math.Max(LeftX, RightX); i++)
                            {
                                double Part = ((double)Math.Abs(i - LeftX)) / dif;
                                PaintPoint(i, j, Part * RightZ + (1 - Part) * LeftZ,
                                Part * RightTexX + (1 - Part) * LeftTexX, Part * RightTexY + (1 - Part) * LeftTexY, ref texture);
                            }
                        }
                    }

                
            }


        private void PaintTriangle(ref TPoint3D[] Pts)
        {
            if (Pts[0].y < Pts[1].y) { Swap(ref Pts[0], ref Pts[1]); }
            if (Pts[1].y < Pts[2].y) { Swap(ref Pts[1], ref Pts[2]); }
            if (Pts[0].y < Pts[1].y) { Swap(ref Pts[0], ref Pts[1]); }

            if (Pts[0].y == Pts[1].y)
            {
                Int16 dif = (Int16)Math.Abs(Pts[0].x - Pts[1].x);
                if (dif == 0) { dif = 1; }
                for (Int16 i = Math.Min(Pts[0].x, Pts[1].x); i <= Math.Max(Pts[0].x, Pts[1].x); i++)
                {
                    double Part = ((double)Math.Abs(i - Pts[0].x)) / dif;
                    PaintPoint(i, Pts[0].y, Part * Pts[1].z + (1 - Part) * Pts[0].z);
                }
            }
            else
            {
                Int16 dy1 = (Int16)(Pts[0].y - Pts[2].y);
                Int16 dy2 = (Int16)(Pts[0].y - Pts[1].y);
                for (Int16 j = Pts[1].y; j <= Pts[0].y; j++)
                {
                    double Part1 = (double)(Pts[0].y - j) / dy1;
                    double Part2 = (double)(Pts[0].y - j) / dy2;
                    Int16 LeftX = (Int16)(Part1 * Pts[2].x + (1 - Part1) * Pts[0].x);
                    Int16 RightX = (Int16)(Part2 * Pts[1].x + (1 - Part2) * Pts[0].x);
                    double LeftZ = Part1 * Pts[2].z + (1 - Part1) * Pts[0].z;
                    double RightZ = Part2 * Pts[1].z + (1 - Part2) * Pts[0].z;
                    Int16 dif = (Int16)Math.Abs(LeftX - RightX); if (dif == 0) { dif = 1; }
                    for (Int16 i = Math.Min(LeftX, RightX); i <= Math.Max(LeftX, RightX); i++)
                    {
                        double Part = ((double)Math.Abs(i - LeftX)) / dif;
                        PaintPoint(i, j, Part * RightZ + (1 - Part) * LeftZ);
                    }
                }
            }

            ////////////\\\\\\\\\\\////////\\\\\\\\\\\\
            if (Pts[1].y == Pts[2].y)
            {
                Int16 dif = (Int16)Math.Abs(Pts[1].x - Pts[2].x);
                if (dif == 0) { dif = 1; }
                for (Int16 i = Math.Min(Pts[2].x, Pts[1].x); i <= Math.Max(Pts[2].x, Pts[1].x); i++)
                {
                    double Part = ((double)Math.Abs(i - Pts[2].x)) / dif;
                    PaintPoint(i, Pts[1].y, Part * Pts[1].z + (1 - Part) * Pts[2].z);
                }
            }
            else
            {
                Int16 dy1 = (Int16)(Pts[0].y - Pts[2].y);
                Int16 dy2 = (Int16)(Pts[1].y - Pts[2].y);
                for (Int16 j = Pts[2].y; j <= Pts[1].y; j++)
                {
                    double Part1 = (double)(j - Pts[2].y) / dy1;
                    double Part2 = (double)(j - Pts[2].y) / dy2;
                    Int16 LeftX = (Int16)(Part1 * Pts[0].x + (1 - Part1) * Pts[2].x);
                    Int16 RightX = (Int16)(Part2 * Pts[1].x + (1 - Part2) * Pts[2].x);
                    double LeftZ = Part1 * Pts[0].z + (1 - Part1) * Pts[2].z;
                    double RightZ = Part2 * Pts[1].z + (1 - Part2) * Pts[2].z;
                    Int16 dif = (Int16)Math.Abs(LeftX - RightX); if (dif == 0) { dif = 1; }
                    for (Int16 i = Math.Min(LeftX, RightX); i <= Math.Max(LeftX, RightX); i++)
                    {
                        double Part = ((double)Math.Abs(i - LeftX)) / dif;
                        PaintPoint(i, j, Part * RightZ + (1 - Part) * LeftZ);
                    }
                }
            }


        }
    }
}
